Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Fish support for environments #15503

Open
wants to merge 25 commits into
base: develop2
Choose a base branch
from

Conversation

AbrilRBS
Copy link
Member

@AbrilRBS AbrilRBS commented Jan 23, 2024

Changelog: Feature: Add Fish shell command support for environments
Docs: conan-io/docs#3690

Still need to check if the -g scope for the variable erasure is ok, and how activating both :powershell and :fish confs work

Copy link
Member

@memsharded memsharded left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, just I agree it would need a bit more testing of functionality and possible interactions with other shells.

@uilianries
Copy link
Member

Well, I'm officially converted to the cult of Fish now, so I'm interested as well.

I'll take a look with Ruben to push this PR again.

@uilianries uilianries self-assigned this Mar 22, 2024
Comment on lines 549 to 554
value = varvalues.get_str("${name}", self._subsystem, pathsep=self._pathsep)
value = value.replace('"', '\\"')
if value:
result.append('set -gx {} "{}"'.format(varname, value))
else:
result.append('set -ge {}'.format(varname))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess it could be simplified by using set -p (prepend), instead of expanding all values.

https://fishshell.com/docs/current/cmds/set.html

Copy link
Member

@memsharded memsharded left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit risky in its current form, because it breaks when applied to dependencies or conan build

result.append('set -ge {}'.format(varname))

content = "\n".join(result)
content = relativize_generated_file(content, self._conanfile, "$script_folder")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't it possible to not use this and relativize paths as the other environment do?

else: # Need to deduce it automatically
is_bat = self._subsystem == WINDOWS
is_ps1 = self._conanfile.conf.get("tools.env.virtualenv:powershell", check_type=bool)
if is_ps1:
is_fish = self._conanfile.conf.get("tools.env.virtualenv:fish", check_type=bool)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are no wrappers for .fish files, are they? If there are no wrappers, this will break when set and used to build any dependency.
This should most likely apply only to end-consumer conanfile, not cache dependencies, and only if not used for conan build or anything like that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, please, take a look in the test test_transitive_tool_requires. It generates a tool requirement, then a second recipe consumes it and runs an executable that prints an environment variable. All using Fish.

deactivate = ("""echo "echo Nothing to restore" > {deactivate_file}"""
.format(deactivate_file=deactivate_file))
else:
deactivate = textwrap.dedent("""\
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For new one, I'd try to do directly an in-memory or in-tmp-folder deactivate script rather than a local generated one.

@uilianries
Copy link
Member

To understand the current error when trying to use conanrun.sh with fish:

⋊> source conanrun.sh 
/private/tmp/foo/conanrunenv-release-armv8.sh (line 1): Unsupported use of '='. In fish, please use 'set script_folder "/private/tmp/foo"'.
script_folder="/private/tmp/foo"
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
from sourcing file /private/tmp/foo/conanrunenv-release-armv8.sh
	called on line 1 of file conanrun.sh
from sourcing file conanrun.sh
.: Error while reading file '/private/tmp/foo/conanrunenv-release-armv8.sh'

So, it's incompatible with Fish right now. Just to have an idea, pyenv generates both activate and activate.fish.

I'll check if we can do something compatible with both, because it's a really small script that we have.

@uilianries
Copy link
Member

Add new test to validate consuming a tool requirement using Fish shell. It should be able to load the tool without errors.

# TODO: This implemantation is dirty, improve it
for f in filenames:
f = f if os.path.isabs(f) else os.path.join(env_folder, f)
if f.lower().endswith(".sh"):
if os.path.isfile(f) and "sh" in accept:
f = subsystem_path(subsystem, f)
shs.append(f)
elif f.lower().endswith(".fish"):
if os.path.isfile(f) and "fish" in accept:
f = subsystem_path(subsystem, f)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should be supporting fish at the moment in Windows subsystems, mostly used externally with win_bash

@@ -62,6 +73,10 @@ def environment_wrap_command(env_filenames, env_folder, cmd, subsystem=None,
elif shs:
launchers = " && ".join('. "{}"'.format(f) for f in shs)
return '{} && {}'.format(launchers, cmd)
elif fishs:
launchers = " && ".join('. "{}"'.format(f) for f in fishs)
print(f"LAUNCHERS: {launchers}")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
print(f"LAUNCHERS: {launchers}")

Comment on lines 548 to 556
function {{ function_name }}
echo "echo Restoring environment"
{% for item in vars_unset %}
set -e {{ item }}
{% endfor %}
{% for item, value in vars_restore.items() %}
set -gx {{ item }} {{ value }}
{% endfor %}
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The restore should work restoring the variables that were defined at activation time, not the variables that were defined at the time of generating the script. It might be a bit more complicated, the activation needs to store the variables in memory or something too, so the restore can use them

@@ -167,6 +168,8 @@ def deactivates(filenames):
bats.append("%~dp0/"+path)
elif env_script.endswith(".sh"):
shs.append(subsystem_path(subsystem, path))
elif env_script.endswith(".fish"):
fishs.append(subsystem_path(subsystem, path))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above, no subsystem for fish initially, IMHO

@uilianries
Copy link
Member

uilianries commented Apr 12, 2024

@memsharded Thank you for your early review. I just talked to @RubenRBS few minutes ago and found other cases to be testes using spaces and some alternatives for deactivation via memory function.

Most of these changes were made to track the creation of conanbuild.sh and how conan_run invokes with wrapper. I have to confess I spent few hours to understand the flow.

Copy link
Member

@memsharded memsharded left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! I think it is almost ready.

:param file_location: The path to the file to save the fish script.
"""
filepath, filename = os.path.split(file_location)
function_name = f"deactivate_{Path(filename).stem}"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the function_name is this one? Why not just the group with the random chars? Is there risk of collision of deactivate_xxxx function name?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It follow the common name used by other virtualenv like: conanbuild.sh -> deactivate_conanbuild.sh. So you can actually can call from your terminal directly, like:

source conanbuild.fish
...
deactivate_conanbuild

In Fish, functions are exported as a command, so you can run directly.

About the random to avoid collision: In case we have multiple dependencies add bin folder to PATH, we will need to undo the configuration after deactivating it. Usually it would be store in a deactivate script, but here we are using a variable. Actually, the random is just an idea, but could be the build folder name too, because is kind unique name.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, having something deterministic would make more sense, I probably still didn't understand the issue.

Comment on lines 561 to 563
{% if item == "PATH" %}
set -g conan_{{ group }}_{{ item }} "${{ item }}"
fish_add_path -pg "{{ value }}"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are other variables that are paths too, like PYTHONPATH, and every other var marked as "path", shouldn't they do the same?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately fish_add_path is a feature only to PATH: https://fishshell.com/docs/current/cmds/fish_add_path.html

I didn't find a similar feature for custom paths, but I'll check with Rubén tomorrow for insights.

@uilianries
Copy link
Member

uilianries commented Apr 15, 2024

I'm not sure about the logic that I added: In case a variable already exist, it will be prepend to that variable.

Pros: Preserves system variables and variables already configured by another Conan dependency.

Cons: If an user runs conanbuild.fish twice, all variables will be broken as they will be value duplicated.

I still need to fix some important points when manipulating paths, it's not working right now when needing to prepend.

Signed-off-by: Uilian Ries <[email protected]>
Signed-off-by: Uilian Ries <[email protected]>
Signed-off-by: Uilian Ries <[email protected]>
Signed-off-by: Uilian Ries <[email protected]>
Signed-off-by: Uilian Ries <[email protected]>
@uilianries

This comment was marked as resolved.

Signed-off-by: Uilian Ries <[email protected]>
@uilianries uilianries marked this pull request as ready for review April 22, 2024 14:33
@uilianries
Copy link
Member

Fish is now installed and configured with tests. Time for review!

from conans.util.files import save

# INFO: Fish only supports Cygwin and WSL https://github.com/fish-shell/fish-shell?tab=readme-ov-file#windows
pytestmark = pytest.mark.skipif(platform.system() not in ("Darwin", "Linux"), reason="Fish is well supported only in Linux and MacOS")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this syntax and not a normal @pytest.mark.skip?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because the entire file only work with Linux and MacOS. Otherwise, would need to apply for each test. It's a documented feature in pytest: https://docs.pytest.org/en/latest/example/markers.html#marking-whole-classes-or-modules

return ". " + " && . ".join('"{}"'.format(s) for s in files)
fishs = [it for it in fishs if os.path.exists(it)]
if fishs:
filename = "conan{}.fish".format(group)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here it seems it is missing the deactivation function (not script)?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually the deactivation is not an script, but a function exported by Fish in the environment. So we can call the deactivate_conanbuild, but there is no file associated. The function is created on-the-fly: https://github.com/conan-io/conan/pull/15503/files#diff-56b36c23589a66b0bd803c545703684c74400cbb7bd8538b8949ed84613e3fc7R550

:param file_location: The path to the file to save the fish script.
"""
filepath, filename = os.path.split(file_location)
function_name = f"deactivate_{Path(filename).stem}"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, having something deterministic would make more sense, I probably still didn't understand the issue.

@AbrilRBS AbrilRBS modified the milestones: 2.4.0, 2.5.0 Jun 4, 2024
@AbrilRBS AbrilRBS modified the milestones: 2.5.0, 2.6.0 Jul 1, 2024
@AbrilRBS AbrilRBS modified the milestones: 2.6.0, 2.7.0 Jul 31, 2024
@AbrilRBS AbrilRBS modified the milestones: 2.7.0, 2.8.0 Aug 27, 2024
@czoido czoido modified the milestones: 2.8.0, 2.9.0 Sep 24, 2024
@AbrilRBS AbrilRBS modified the milestones: 2.9.0, 2.10.0 Oct 28, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants